//
// Created by LiuYou on 2021/11/1.
//

#include <iostream>
#include <fcntl.h>
#include <unistd.h>
//#include <poll.h>

#include <sys/epoll.h>


enum class FSMState /*: int*/ {
    R = 1,
    W,
    E,
    T
};


/*
 * 这个有限状态机的封装其实就是一次 MyCopy 的现场。
 * 这是新的方式和模型重构了之前的 MyCopy 代码。
 */
class FiniteStateMachine {
public:
    FiniteStateMachine(FSMState state, int srcFd, int destFd) : state( state ), srcFd( srcFd ), destFd( destFd ) {}

    ~FiniteStateMachine() = default;

public:
    FSMState state;
    int srcFd;
    int destFd;

public:
    char buf[1024]{};

public:
    ssize_t readBytes{};

public:
    // 这个资源不是这个类自己管理的, 其实不推荐这种用法。
//    char* errStr{};

    // 这个错误字符串的内存由这个类产生的对象管理, 推荐的用法。
    char errStr[10]{};


};


/**/
static const char* const TTY5 = "/dev/tty5";
static const char* const TTY6 = "/dev/tty6";

/**/
static int fdTty5;
static int fdTty6;


static FiniteStateMachine& fsmDriver(FiniteStateMachine& finiteStateMachine) {
    switch (finiteStateMachine.state) {

        case FSMState::R: {
            finiteStateMachine.readBytes =
                    read( finiteStateMachine.srcFd,
                          finiteStateMachine.buf,
                          sizeof( finiteStateMachine.buf ));
            if (finiteStateMachine.readBytes == 0) {
                finiteStateMachine.state = FSMState::T;
            } else if (finiteStateMachine.readBytes < 0) {
                if (errno == EAGAIN || errno == EINTR) {
                    finiteStateMachine.state = FSMState::R;
                } else {
//                        finiteStateMachine12.errStr = "read()";
                    sprintf( finiteStateMachine.errStr, "read()" );
                    finiteStateMachine.state = FSMState::E;
                }
            } else {
                finiteStateMachine.state = FSMState::W;
            }

            break;
        }
        case FSMState::W: {
            ssize_t pos{};
            while (true) {
                ssize_t writeBytes =
                        write( finiteStateMachine.destFd,
                               finiteStateMachine.buf + pos,
                               finiteStateMachine.readBytes );
                pos += writeBytes;
                finiteStateMachine.readBytes -= writeBytes;
                if (finiteStateMachine.readBytes == 0) {
                    // ...
                    finiteStateMachine.state = FSMState::R;
                    break;
                } else if (writeBytes < 0) {
                    if (errno == EAGAIN || errno == EINTR) {
                        continue;
                    } else {
//                            finiteStateMachine12.errStr = "write()";
                        sprintf( finiteStateMachine.errStr, "write()" );
                        finiteStateMachine.state = FSMState::E;
                        break;
                    }
                } else {
                    continue;
                }
            }

            break;
        }
        case FSMState::E: {
            // 想要报错, 但是不知道是读出错还是写出错。
            // 那么怎么解决呢?
            // 可以在class有限状态机中加一个字段记录是
            // 写的时候出错或者读的时候出错的字符串。
            fprintf( stderr, "%s 失败了.\n", finiteStateMachine.errStr );
            finiteStateMachine.state = FSMState::T;
            break;
        }
        case FSMState::T: {
            /*
             * 应该是进程结束。
             * 当时在这个程序中结束不了,
             * 因为 这两个设备是没有办法终止的。
             */

            break;
        }
        default: {
            abort();
            break;
        }

    }

    return finiteStateMachine;
}


static void relay() {
    int oldFdTty5Status = fcntl( fdTty5, F_GETFL );
    int newFdTty5Status = fcntl( fdTty5, F_SETFL, oldFdTty5Status | O_NONBLOCK );
    int oldFdTty6Status = fcntl( fdTty6, F_GETFL );
    int newFdTty6Status = fcntl( fdTty6, F_SETFL, oldFdTty6Status | O_NONBLOCK );

    // 读 5 写 6
    FiniteStateMachine finiteStateMachine12( FSMState::R, fdTty5, fdTty6 );
    // 读 6 写 5
    FiniteStateMachine finiteStateMachine21( FSMState::R, fdTty6, fdTty5 );


//    fd_set readFdSet;
//    fd_set writeFdSet;
//    // 并不关心文件描述符发生异常的行为, 所以异常的这个集合
//    // 可以写成 nullptr。
////    fd_set exceptionFdSet;


//    pollfd pollFd[2];
//    pollFd[0].fd = fdTty5;
//    pollFd[1].fd = fdTty6;

    int epollFd = epoll_create( 5 );
    if (epollFd < 0) {
        perror( "epoll_create();" );
        exit( 1 );
    }



    // 推状态机
    while (finiteStateMachine12.state != FSMState::T ||
           finiteStateMachine21.state != FSMState::T) {

        // 不能盲目的推状态机。
        // 布置监视任务。

        epoll_event epollEvent[2]{};


        // 每次布置的任务是不同的, 所以把 events字段位图清为0。
//        pollFd[0].events = 0;
//        pollFd[1].events = 0;
        if (finiteStateMachine12.state == FSMState::R) {
//            FD_SET( finiteStateMachine12.srcFd, &readFdSet );
//            pollFd[0].events |= POLLIN;
            epollEvent[0].events |= EPOLLIN;
            epoll_ctl( epollFd, EPOLL_CTL_ADD, fdTty5, &epollEvent[0] );
        }
        if (finiteStateMachine12.state == FSMState::W) {
//            FD_SET( finiteStateMachine12.srcFd, &writeFdSet );
//            pollFd[1].events |= POLLOUT;
            epollEvent[1].events |= EPOLLOUT;
            epoll_ctl( epollFd, EPOLL_CTL_ADD, fdTty6, &epollEvent[1] );
        }
        if (finiteStateMachine21.state == FSMState::R) {
//            FD_SET( finiteStateMachine21.srcFd, &readFdSet );
//            pollFd[1].events |= POLLIN;
            epollEvent[1].events |= EPOLLIN;
            epoll_ctl( epollFd, EPOLL_CTL_ADD, fdTty6, &epollEvent[1] );
        }
        if (finiteStateMachine21.state == FSMState::W) {
//            FD_SET( finiteStateMachine21.srcFd, &writeFdSet );
//            pollFd[0].events |= POLLOUT;
            epollEvent[0].events |= EPOLLOUT;
            epoll_ctl( epollFd, EPOLL_CTL_ADD, fdTty5, &epollEvent[0] );
        }


        epoll_event epollEventResult[2]{};

        // 监视
        // 查看监视结果
        // 根据监视结果有条件的推动状态机

//        while (true) {
//            int readyEpollEvents = epoll_wait( epollFd, epollEventResult, 2, -1 );
//            if (readyEpollEvents < 0) {
//                perror( "epoll_wait()" );
//                exit( 1 );
//            }
//            if (errno == EINTR) {
//                continue;
//            }
//            for (int i = 0; i < readyEpollEvents; ++i) {
////                // 如果1可读, 2可写。
////                if (epollEventResult[0].events & EPOLLIN ||
////                    epollEventResult[1].events & EPOLLOUT) {
////                    finiteStateMachine12 = fsmDriver( finiteStateMachine12 );
////                }
////                if (epollEventResult[1].events & EPOLLIN ||
////                    epollEventResult[0].events & EPOLLOUT) {
////                    finiteStateMachine21 = fsmDriver( finiteStateMachine21 );
////                }
//
//            }
//        }


        while (epoll_wait( epollFd, epollEventResult, 2, -1 ) < 0) {
            if (errno == EINTR) {
                continue;
            }
            perror( "epoll();" );
            exit( 1 );
        }

        // 查看监视结果
        // 根据监视结果有条件的推动状态机

        // 如果1可读, 2可写。
        if (epollEventResult[0].events & EPOLLIN ||
            epollEventResult[1].events & EPOLLOUT) {
            finiteStateMachine12 = fsmDriver( finiteStateMachine12 );
        }
        if (epollEventResult[1].events & EPOLLIN ||
            epollEventResult[0].events & EPOLLOUT) {
            finiteStateMachine21 = fsmDriver( finiteStateMachine21 );
        }


    }

    fcntl( fdTty5, F_SETFL, oldFdTty5Status );
    fcntl( fdTty6, F_SETFL, oldFdTty6Status );

    close( epollFd );
}


int main(int argc, char** argv) {
//    TTY5
//    TTY6

    fdTty5 = open( TTY5, O_RDWR );
    if (fdTty5 < 0) {
        perror( "open()" );
        return 1;
    }
    atexit( [] {
        close( fdTty5 );
    } );
    write( fdTty5, "tty5\n", 5 );

    fdTty6 = open( TTY6, O_RDWR | O_NONBLOCK );
    if (fdTty6 < 0) {
        perror( "open()" );
        return 1;
    }
    atexit( [] {
        close( fdTty6 );
    } );
    write( fdTty6, "tty6\n", 5 );

    // relay();
    relay();


    return 0;
}
